fix(ui+tray): tool quarantine diff as side-by-side before/after#391
Merged
Conversation
The quarantine panel previously rendered the current description as a plain paragraph and, below it, a single diff box that mixed added, removed, and same tokens — making it look like the same text was being shown twice with random green highlights. Split the diff into two labelled boxes so each side only renders its own words: the "Before (approved)" box shows the previous description (with any removed words highlighted red), and the "After (current)" box shows the new description (with added words highlighted green). The plain paragraph is suppressed when a diff is available so the current text is not duplicated above the "After" box. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deploying mcpproxy-docs with
|
| Latest commit: |
bcc25ef
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://0f805aa5.mcpproxy-docs.pages.dev |
| Branch Preview URL: | https://fix-ui-tool-diff-before-afte.mcpproxy-docs.pages.dev |
📦 Build ArtifactsWorkflow Run: View Run Available Artifacts
How to DownloadOption 1: GitHub Web UI (easiest)
Option 2: GitHub CLI gh run download 24579082185 --repo smart-mcp-proxy/mcpproxy-go
|
Bring the macOS tray's Tool Quarantine diff in line with the web UI's
new before/after layout. Previously the tray stacked a red "minus"
box above a green "plus" box containing the raw old/new text, which
gave no hint about which specific words actually changed. Now the
diff section renders two labelled boxes ("Before (approved)" and
"After (current)") built from a longest-common-subsequence word diff;
each side only shows its own words with removals/additions
highlighted inside their respective boxes via AttributedString
background color.
Ports the same LCS used in ServerDetail.vue (split-on-whitespace
tokens, backtrack to build parts, merge consecutive parts of the same
kind). Adds ToolDiffTests covering identical inputs, empty sides, the
real gcore short→docstring expansion, and reconstruction invariants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The word-level diff highlighted the entire differing token — so "1 April" → "8 April" lit up the whole "1"/"8" tokens, and more insidiously "version-1.2.3" → "version-1.2.4" lit up the entire version string. Now we do a two-pass diff: word-level LCS for coarse alignment, then for each adjacent (removed, added) pair we refine with a character-level LCS. The result highlights only the characters that actually changed (a single "1" vs "8", or "3" vs "4" inside a version token), while large docstring expansions continue to highlight cleanly at the word level because they have no paired removed run to refine against. Includes a safety cap (`maxChars = 1500`) that falls back to the raw removed/added pair when a run is too long, so the O(N×M) char-level dp table can never blow up on giant payloads. Ports the refined algorithm to both ServerDetail.vue and ServerDetailView.swift and extends ToolDiffTests with coverage for "1 April" → "8 April", "version-1.2.3" → "version-1.2.4", and the long-run safeguard. Verified live in Chrome against hugginface/hf_doc_search (the real "1 April" → "8 April" case): only one char is highlighted on each side (`removedTexts: ["1"]`, `addedTexts: ["8"]`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ard section Three related UI stability fixes: 1. **Stable server order across API polls.** `Runtime.GetAllServers` iterates the Supervisor StateView's `map[string]*ServerStatus`, which has non-deterministic iteration order in Go. Every poll returned the same servers in a different order, so filtered views like the tray's "Servers Needing Attention" shuffled every few seconds (a server would be first, then third, then second). Sort by name at the end of GetAllServers — stable alphabetical, deterministic across polls. 2. **Stable Recent Sessions order.** `Runtime.GetRecentSessions` relied on BBolt cursor order (newest-started-first by storage key). The tray's dashboard then deduped by client name and re-sorted by tool call count — which is zero for all sessions, so the final order came from `Dictionary.values` iteration and reshuffled each poll. Sort in the backend by `LastActivity` desc (break ties by ID), and replace the tray's tool-count sort with `lastActive` desc + tool-count + session ID as tiebreakers. Dashboard now shows sessions top-to-bottom by actual recency. 3. **Dashboard "Recent Tool Calls" → "Recent Activity".** The section filtered `recentActivity` down to tool_call/internal_tool_call types only, so users without any indexed tool calls saw "No tool calls recorded" despite having plenty of real activity (security scans, tool quarantine changes, OAuth events). Rename the section, widen the filter to all activity types except low-signal system events (`system_start`, `system_stop`), and rename the column header from "Tool" to "Event". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Activity Log used an inner `HSplitView` for its list + detail layout, nested inside the main window's `NavigationSplitView`. When the detail panel expanded on row click, the inner HSplitView's width demands competed with the outer sidebar column and collapsed it below its minimum — so the sidebar's "Dashboard / Servers / Activity Log / …" labels got truncated or hidden. Replace `HSplitView` with a plain `HStack` that renders the detail pane only when an entry is selected, tighten the list's fixed column widths a few pts so the list + detail + sidebar all fit at the default 800pt window width, and add a close (X) button in the detail header for an explicit dismissal affordance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a tool's description changes on an upstream server, both the web UI and the macOS tray's Tool Quarantine panel rendered the current description as a plain paragraph and, just below it, a single diff box that mixed added, removed, and same tokens. This made it look like the same text was being shown twice with random green highlights — users couldn't tell what actually changed and assumed the "changed" flag was spurious.
This PR fixes both surfaces to use a side-by-side before/after layout driven by a longest-common-subsequence word diff:
Same-tokens render plainly in both boxes so the eye can anchor, but each side only contains its own words.
Changes
Web UI (
frontend/src/views/ServerDetail.vue):{{ tool.description }}paragraph when a diff is available (previously caused the text to appear duplicated above the diff)macOS tray (
native/macos/MCPProxy/MCPProxy/Views/ServerDetailView.swift):computeWordDiff,ToolDiffPart)diffLine(isOld:)withdiffBeforeAfter(previous:current:)renderingAttributedStringspans so removals/additions highlight inline inside their respective boxesMCPProxyTests/ToolDiffTests.swiftcovering identical inputs, empty sides, the real gcore short→docstring expansion, merging of consecutive same-kind parts, and the reconstruction invariant (before = same + removed,after = same + added)Context
Triggered by the
gcore-mcp-servercase: the server upgraded its MCP metadata and tool descriptions went from one-liners like"Get the list of Alibaba Cloud regions."to full docstrings documenting newlimit/offsetpagination params (schemas also gained those properties). These are genuine changes — the hash detector, backend, and original diff component all worked correctly — but the UI made the real diff illegible, which the user reported as "text is the same, so no actual changes."Test plan
make build) — no TypeScript errors, bundle produced./mcpproxy serveagainst existing DB with livegcore-mcp-serverchanged-tool records/ui/servers/gcore-mcp-serverin Chrome and inspected multiple changed tools (cdn_cdn_resources_ls,cdn_list_alibaba_regions,cdn_list_aws_regions,cdn_logs_uploader_configs_ls) — each now shows a clean "Before" box with the old description and an "After" box with only the new Args docs highlighted in greenprevious_description) still render the plain description paragraph as beforeswiftcagainst macOS 13 SDK) — clean compile, binary containsBEFORE (APPROVED)andcomputeWordDiffsymbolscomputeWordDiffinvariants (identical, empty before, empty after, real gcore case, reconstruction, merging) — 17/17 passedAttributedStringapproach matches existing annotation badges in the tray)🤖 Generated with Claude Code